﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SimpleActor.Test
{
	[TestClass]
    public class MessageSchedulerSynchronizationContextTest
	{
		[TestMethod]
        public void PostExternallyTest()
		{
            var delay = TimeSpan.FromMilliseconds(250);
            var threshold = TimeSpan.FromMilliseconds(50);

            TestServiceScope.CreateDetail((proxy, scheduler, servant) =>
            {
                var sw = Stopwatch.StartNew();
                var context = scheduler.SynchronizationContext;

                var time1 = sw.Elapsed;
                context.Post(_ => servant.LongAction1(0, delay), null);
                var time2 = sw.Elapsed;

                var parameter = proxy.GetParameter().Result;
                var time3 = sw.Elapsed;

                parameter.Is(Tuple.Create<byte>(0));
                (time2 - time1 < threshold).IsTrue();
                (time3 - time1 >= delay).IsTrue();

                Debug.WriteLine(@"Time: {0:ss\.fff}, {1:ss\.fff}, {2:ss\.fff}", time1, time2, time3);
                Debug.WriteLine("Result: {0} / {1}", parameter.GetType(), parameter);
            });
		}

        [TestMethod]
        public void PostInternallyTest()
        {
            SchedulingTestServiceScope.Create(service =>
            {
                var info0 = service.GetCountInfo();

                Debug.WriteLine("info0:{0}", info0.Result);

                info0.Result.Is(new CountInfo(0, 0));
                
                var info1a = service.DoPost(3);
                var info1b = service.GetCountInfo();

                Debug.WriteLine("info1a:{0}, info1b:{1}", info1a.Result, info1b.Result);

                info1a.Result.Is(new CountInfo(1, 0));
                info1b.Result.Is(new CountInfo(2, 3));

            });
        }

        [TestMethod]
        public void PostInternallyOnStopTest()
        {
            var servant = new SchedulingTestServant(SchedulingTestMode.PostOnStop);
            var scheduler = new MessageScheduler<ISchedulingTestServant>("Service", servant);
            var proxy = new SchedulingTestServiceProxy(scheduler);

            var start = proxy.Start();
            AssertEx.DoesNotThrow(() => start.Wait());

            var stop = proxy.Stop();
            AssertEx.Throws<AggregateException>(() => stop.Wait());

            stop.Exception.InnerExceptions.Count.Is(1);
            stop.Exception.InnerException.IsInstanceOf<InvalidOperationException>();
            Debug.WriteLine("Stop:");
            Debug.Indent();
            Debug.WriteLine(stop.Exception);
            Debug.Unindent();
        }

        [TestMethod]
        public void SendExternallyTest()
        {
            var delay = TimeSpan.FromMilliseconds(250);
            var threshold = TimeSpan.FromMilliseconds(30);

            TestServiceScope.CreateDetail((proxy, scheduler, servant) =>
            {
                var sw = Stopwatch.StartNew();
                var context = scheduler.SynchronizationContext;

                var time1 = sw.Elapsed;
                context.Send(_ => servant.LongAction1(0, delay), null);
                var time2 = sw.Elapsed;

                var parameter = proxy.GetParameter().Result;
                var time3 = sw.Elapsed;

                parameter.Is(Tuple.Create<byte>(0));
                (time2 - time1 >= delay).IsTrue();
                (time3 - time2 <= threshold).IsTrue();

                Debug.WriteLine(@"Time: {0:ss\.fff}, {1:ss\.fff}, {2:ss\.fff}", time1, time2, time3);
                Debug.WriteLine("Result: {0} / {1}", parameter.GetType(), parameter);
            });
        }

        [TestMethod]
        public void SendInternallyTest()
        {
            SchedulingTestServiceScope.Create(service =>
            {
                var info0 = service.GetCountInfo();

                Debug.WriteLine("info0:{0}", info0.Result);

                info0.Result.Is(new CountInfo(0, 0));

                var info1a = service.DoSend(3);
                var info1b = service.GetCountInfo();

                Debug.WriteLine("info1a:{0}, info1b:{1}", info1a.Result, info1b.Result);

                info1a.Result.Is(new CountInfo(1, 3));
                info1b.Result.Is(new CountInfo(2, 3));

            });
        }

        [TestMethod]
        public void SendInternallyOnStopTest()
        {
            var servant = new SchedulingTestServant(SchedulingTestMode.SendOnStop);
            var scheduler = new MessageScheduler<ISchedulingTestServant>("Service", servant);
            var proxy = new SchedulingTestServiceProxy(scheduler);

            var start = proxy.Start();
            AssertEx.DoesNotThrow(() => start.Wait());

            var stop = proxy.Stop();
            AssertEx.DoesNotThrow(() => stop.Wait());
        }

        [TestMethod]
        public void CreateCopyTest()
        {
            TestServiceScope.CreateDetail((proxy, scheduler, servant) =>
            {
                var sw = Stopwatch.StartNew();
                var context = (MessageScheduler<ITestServant>.MessageSchedulerSynchronizationContext) scheduler.SynchronizationContext;
                var copy = (MessageScheduler<ITestServant>.MessageSchedulerSynchronizationContext)context.CreateCopy();

                copy.IsNotSameReferenceAs(context);

                Assert.AreSame(context.AsDynamic().parent, copy.AsDynamic().parent);
            });
        }
	}
}
